resource.AddFile("sound/StargateExtras/ag3_firing.wav")
resource.AddFile("models/The_Sniper_9/AG3/ag3.mdl")

AddCSLuaFile("cl_init.lua")
AddCSLuaFile("shared.lua")
AddCSLuaFile("states.lua")

include("shared.lua")
include("autorun/server/StargateExtras.lua")
include("includes/enum/disposition.lua")

ENT.cycleInterval = 0.1
ENT.state = nil
ENT.nextState = ENT.States.Idle.New()
ENT.target = nil
ENT.group = {}
ENT.isArmedByWire = false
ENT.isArmedByManual = false

ENT.Damage = {}
ENT.Damage.min = 100
ENT.Damage.current = 1000
ENT.Damage.max = 10000
ENT.Damage.radius = 100

local defaultModel = "models/The_Sniper_9/AG3/ag3.mdl"
local lastUseTime = 0

function ENT:SetNextState(state, ...)
  if(state == nil || state.New == nil || state.id == nil) then
    error(tostring(state) .. " is not a valid state.\n")
  end

  self.nextState = state.New(unpack(arg))
  
  return self.nextState
end

function ENT:GetTarget()
  return self.target
end

function ENT:SetTarget(pos)
  self.target = pos
end

function ENT:GetEnergyPerShot()
  return self.Damage.current 
end

function ENT:IsGrouped()
  local group = self:GetGroup()
  
  return group ~= nil && #group > 1
end

function ENT:GetGroup()
  return self.group or { self }
end

function ENT:SetGroup(group)
  self.group = group or { self }
end

function ENT:GroupWith(otherAg3)
  if(ValidEntity(otherAg3) == false) then
    error("Invalid entity to group with.\n")
  end

  local currentGroup = self:GetGroup()
  local otherGroup = otherAg3:GetGroup()
  
  if(currentGroup == otherGroup) then
    return
  end
  
  for _, ag3 in ipairs(currentGroup) do
    if(ValidEntity(ag3)) then
      table.insert(otherGroup, ag3)
      ag3:SetGroup(otherGroup)
    end
  end
end

function ENT:Ungroup()
  local group = self:GetGroup()

  for index, ag3 in ipairs(group) do
    if(ag3 == self) then
      table.remove(group, index)
      break
    end
  end
  
  self:SetGroup(nil)
end

function ENT:SpawnFunction(player, trace)
	if(!trace.Hit) then 
    return 
  end
   
	local entity = ents.Create(self:GetClass())
	entity:SetPos(trace.HitPos + trace.HitNormal * entity:BoundingRadius())
  entity:SetVar("Owner", player)
	entity:Spawn()
	entity:Activate()
   
	undo.Create(self:GetClass())
	undo.AddEntity(entity)
	undo.SetPlayer(player)
	undo.Finish()
end

function ENT:Initialize()
	if(util.IsValidModel(self.Entity:GetModel()) == false ||
    self.Entity:GetModel() == "models/error.mdl") then
    
    Msg("Using default model for "..self:GetClass()..".\n")
    
    self.Entity:SetModel(defaultModel)
      
    if(util.IsValidModel(self.Entity:GetModel()) == false) then
      error("No valid model for "..self:GetClass()..".\n")
    end
  end
   
	self.Entity:PhysicsInit(SOLID_VPHYSICS)
  self.Entity:SetMoveType(MOVETYPE_VPHYSICS)
  self.Entity:SetSolid(SOLID_VPHYSICS)
  
  local physics = self.Entity:GetPhysicsObject()
   
	if(physics && physics:IsValid()) then 
    physics:EnableGravity(false)
    physics:EnableDrag(true)
    physics:Wake()
  else
    DebugMsg(tostring(self) .. " has no physics object!")
  end
   
  -- Set health
  self.Entity:SetMaxHealth(300)
  self.Entity:SetHealth(300)
  self.maxhealth = 300
   
  -- Set resources
  if(self.HasRD) then
    self:AddResource("energy", 0)
  end
    
    -- Set up wire inputs and outputs
	if(self.HasWire) then
		self:CreateWireInputs("Arm", "X", "Y", "Z") 
		self:CreateWireOutputs("Armed", "State") 
	end
   
  StargateExtras:RegisterEntity(self)
end

function ENT:Setup(damage)
   self.Damage.current = math.Clamp(damage, self.Damage.min, self.Damage.max)
end

function ENT:IsArmed()
  return self.isArmedByWire || self.isArmedByManual
end

-- Respond to a given wire input
function ENT:TriggerInput(inputName, inputValue)
  if(inputName == "X" || inputName == "Y" || inputName == "Z") then
    if(inputValue ~= nil) then
      if(self.target == nil) then
        self.target = Vector(0, 0, 0)
      end
      
      self.target[string.lower(inputName)] = inputValue
    else
      self.target = nil
    end
    
    if(self.target == Vector(0,0,0)) then
      self.target = nil
    end
  elseif(inputName == "Arm") then
    self.isArmedByWire = (inputValue ~= 0)
  end
end

function ENT:GetNearestTarget()
  local nearestEntity = nil
  local distanceToNearestEntity = self:GetRange() + 1

  local setNearestEntity = function(entity, distance)
    nearestEntity = entity
    distanceToNearestEntity = distance
  end
  
  local ag3Owner = self:GetVar("Owner")
  
  for _, entity in pairs(ents.FindInSphere(self:GetPos(), self:GetRange())) do    
    if(entity.IsCloaked ~= true) then
      local distanceToEntity = entity:GetPos():Distance(self:GetPos())    
      
      if(distanceToEntity < distanceToNearestEntity) then
        if(entity:IsNPC() && ag3Owner ~= nil) then
           local npcDisposition = entity:Disposition(ag3Owner)
           
           if(npcDisposition == D_HT || npcDisposition == D_FR) then
              setNearestEntity(entity, distanceToEntity)
           end
        elseif(entity:IsPlayer() && ag3Owner ~= nil && entity ~= ag3Owner) then
          setNearestEntity(entity, distanceToEntity)
        else
          local entityOwner = entity:GetVar("Owner")
          
          if(entity:IsWorld() == false && entityOwner ~= nil && entityOwner ~= ag3Owner) then
            setNearestEntity(entity, distanceToEntity)
          end
        end
      end
    end
  end
  
  return nearestEntity
end

function ENT:Use()
   if(CurTime() < lastUseTime + 1) then
      return
   end
   
   lastUseTime = CurTime()
   
   self.isArmedByManual = !self.isArmedByManual
end

function ENT:Think()
  self:NextThink(CurTime() + self.cycleInterval)

  if(self.nextState ~= self.state) then
    self.state = self.nextState
    
    if(self.state ~= nil) then
      self.Entity:SetNetworkedInt("state", self.state.id)
    else
      self.Entity:SetNetworkedInt("state", nil)
    end
  end
  
  if(self.state ~= nil) then
    self.state:Run(self)
  end
  
  self:UpdateWireOutputs()
  
	return true
end

-- Updates all wire output values
function ENT:UpdateWireOutputs()
  if(self.HasWire ~= true) then
    return
  end
  
  if(self.state ~= nil) then
    self:SetWire("State", self.state.id)
  else
    self:SetWire("State", nil)
  end
  
  self:SetWire("Armed", self:IsArmed())
end

function ENT:OnTakeDamage(damageInfo)  
   if(self.isDestructing == false && self.isCombatSystemEnabled ~= true) then
      self.Entity:TakePhysicsDamage(damageInfo)
      
      self.Entity:SetHealth(self.Entity:Health() - damageInfo:GetDamage())
       
      if(self.Entity:Health() <= 0) then
         self:Destruct()
      end
   end
end

function ENT:Destruct()
  if(self.isDestructing) then
    return
  end
   
  self.isDestructing = true
   
  local blastRadius = 100
  local blastDamage = 40
   
  destructEffect = EffectData()
	destructEffect:SetOrigin(self:GetPos())
  destructEffect:SetRadius(blastRadius)
	util.Effect("Explosion", destructEffect, true, true)
   
  local owner = self:GetVar("Owner")
  util.BlastDamage(self.Entity, owner, self:GetPos(), blastRadius, blastDamage)
    
	self:Remove()
end

function ENT:OnTakeDamage(damageInfo)
   if(self.state ~= nil && ValidEntity(self.state.beam) && damageInfo:GetInflictor() == self.state.beam) then
      damageInfo:SetDamage(0)
   end
end

function ENT:OnRemove()
  StargateExtras:UnregisterEntity(self)
  StarGate.WireRD.OnRemove(self)
end
